<?php

namespace liketp\database;

use liketp\App;
use liketp\database\concern\ExecuteQuery;
use liketp\database\concern\ResultOperate;
use liketp\database\concern\WhereQuery;
use liketp\database\concern\TableStruct;
use liketp\DatabaseManager;
use liketp\Model;

class Query
{
    use WhereQuery;
    use ExecuteQuery;
    use TableStruct;
    use ResultOperate;

    protected $app;
    protected $manager;
    protected $connector;

    protected $bindData = [];
    protected static $bindDataIndex = 0;

    protected $model;
    protected $pk = '';

    protected $options = [
        'connect' => [],
        'sql' => '',

        'alias' => [],

        'prefix' => '',
        'name' => '',
        'table' => '',

        'replace' => false,

        'using' => [],

        'distinct' => '',
        'extra' => [],

        'field' => [],
        'data' => [],
        'set' => [],

        'partition' => [],

        'force' => '',

        'join' => [],

        'where' => [],

        'group' => '',
        'having' => [],

        'union' => '',

        'order' => [],
        'limit' => [],

        'lock' => '',
        'comment' => '',

        'raw' => '',
        'fetchSql' => false
    ];

    public function __construct(App $app, DatabaseManager $manager)
    {
        $this->app = $app;
        $this->manager = $manager;
        $this->connector = $app['db.connector'];
        $this->prefix($manager->config('prefix', ''));
    }

    public function createQuery(?Query $origin = null, array $needCopyFields = [])
    {
        $query = $this->app->make(Query::class, [], true);

        if ($origin) {

            $defaultCopyFields = ['prefix', 'name', 'table'];
            $needCopyFields = empty($needCopyFields) ? $defaultCopyFields : $needCopyFields;

            if (in_array('*', $needCopyFields)) {
                $needCopyFields = array_keys($origin->getOptions());
            }

            foreach ($needCopyFields as $field) {
                $query->setOption($field, $origin->getOptions($field));
            }
        }
        return $query;
    }


    //------------------------------------------------ 框架内使用的公开方法
    public function setBindData($key, $val): string
    {
        $key = trim($key, '`');
        $key = empty($key) ? '' : '_' . $key;
        $key = ':_pdo_var_' . static::$bindDataIndex++ . $key;
        $this->bindData[$key] = $val;
        return $key;
    }

    public function mergeBindData(array $data, bool $clear = false)
    {
        if ($clear) $this->bindData = [];
        $this->bindData = array_merge($this->bindData, $data);
        return $this;
    }

    public function model(Model $model)
    {
        $this->model = $model;
        return $this;
    }

    public function pk($pk)
    {
        $this->pk = $pk;
        return $this;
    }


    //------------------------------------------------ 配置方法
    public function connect(array $connectConfig)
    {
        return $this->setOption('connect', $connectConfig);
    }

    public function table($table)
    {
        return $this->setOption('table', $table);
    }

    public function name($name)
    {
        return $this->setOption('name', $name);
    }

    public function prefix($prefix)
    {
        return $this->setOption('prefix', $prefix);
    }

    public function order($key, $sort = 'DESC')
    {
        $sort = strtoupper($sort);
        $merge = $sort == 'MERGE' ? true : false;
        $sort = in_array($sort, ['ASC', 'DESC']) ? $sort : 'DESC';

        $order = is_array($key) ? $key : [$key => $sort];

        if ($merge) {
            $order = array_merge($this->getOptions('order', []), $order);
        }
        return $this->setOption('order', $order);
    }

    public function group($group)
    {
        return $this->setOption('group', $group);
    }

    public function limit(int $offset, ?int $len = null)
    {
        if (is_null($len)) {
            $limit = [0, $offset];
        } else {
            $limit = [$offset, $len];
        }
        return $this->setOption('limit', $limit);
    }

    public function page($page = 1, int $size = 10)
    {
        if (is_array($page)) {
            [$page, $size] = $page + [1, 10];
        }
        $offset = max(0, ($page - 1)) * $size;
        return $this->limit($offset, $size);
    }

    public function fetchSql(bool $fetchSql = true)
    {
        return $this->setOption('fetchSql', $fetchSql);
    }

    public function data(array $data, bool $merge = false)
    {
        if ($merge) $data = array_merge($this->getOptions('data', []), $data);
        return $this->setOption('data', $data);
    }

    public function lock($type = 'update')
    {
        if ($type == 'update') {
            return $this->setOption('lock', 'FOR UPDATE');
        } else if ($type == 'share') {
            return $this->setOption('lock', 'LOCK IN SHARE MODE');
        }
    }

    public function comment(string $comment)
    {
        return $this->setOption('comment', $comment);
    }

    public function using($using)
    {
        return $this->setOption('using', $using);
    }


    //------------------------------------------------ 查询方法
    public function field(...$args)
    {
        $fields = array_reduce($args, function ($stack, $item) {
            if (is_array($item)) {
                array_push($stack, ...$item);
            } else {
                array_push($stack, $item);
            }
            return $stack;
        }, []);

        if (in_array('*', $fields)) $fields = ['*'];
        $fields = array_filter($fields, 'is_string');
        $fields = array_filter($fields, 'strlen');
        $fields = array_unique($fields, SORT_STRING);

        return $this->setOption('field', $fields);
    }

    public function join() {}

    public function raw() {}



    //------------------------------------------------ 信息获取方法
    public function getOptions(?string $key = null, $default = null)
    {
        if (empty($key)) {
            return $this->options;
        } else {
            return $this->options[$key] ?? $default;
        }
    }

    public function getBindData(bool $clear = false)
    {
        $data = $this->bindData;
        if ($clear) $this->bindData = [];
        return $data;
    }

    public function getTableName()
    {
        $table = $this->getOptions('table', '');
        if ($table == '') {
            $table = $this->getOptions('prefix', '') . $this->getOptions('name', '');
        }
        return $table;
    }

    public function getPk()
    {
        return $this->pk;
    }

    //------------------------------------------------ protected
    protected function setOption($key, $val, bool $merge = false)
    {
        if (isset($this->options[$key])) {
            if (is_array($this->options[$key]) && $merge) {
                array_push($this->options[$key], $val);
            } else {
                $this->options[$key] = $val;
            }
        }
        return $this;
    }

    protected function isFetch()
    {
        return $this->getOptions('fetchSql', false);
    }
}
